import { getRoomTerrain } from "../../global/map";
import { errLog } from "../../util/log";
import { Serializable } from "../../util/util";
import { Group } from "../group";

const rclStructureAccumulationTable = {
    '0':{
        road: Infinity,
        container: 5,
    },
    '1':{
        spawn: 1,
    },
    '2':{
        extension: 5,
        rampart: Infinity,
        wall: Infinity,
    },
    '3':{
        extension: 5,
        tower: 1,
    },
    '4':{
        extension: 10,
        storage: 1,
    },
    '5':{
        extension: 10,
        tower: 1,
        link: 2,
    },
    '6':{
        extension: 10,
        extractor: 1,
        lab: 3,
        terminal: 1,
        link: 1,
    },
    '7':{
        extension: 10,
        tower: 1,
        lab: 3,
        factory: 1,
        link: 1,
        spawn: 1,
    },
    '8':{
        extension: 10,
        spawn: 1,
        tower: 3,
        link: 2,
        lab: 4,
        observer: 1,
        powerSpawn: 1,
        nuker: 1,
    },
}
export function getMarginalStructureLimit(rcl){
    return rclStructureAccumulationTable[rcl];
}
export function getStructureLimit(rcl){
    let ans = {};
    for(let i=0; i<=rcl; i++){
        let limit = getMarginalStructureLimit(i);
        for(let type in limit){
            if(!ans[type]){
                ans[type] = limit[type];
            }else{
                ans[type] += limit[type];
            }
        }
    }
    return ans;
}
export class PlanedStructure{
    /**
     * 
     * @param {RoomPosition|{x:Number,y:Number,roomName:String}} pos 
     * @param {BuildableStructureConstant|} structureType 
     */
    constructor(pos, structureType){
        this.pos = new RoomPosition(pos.x,pos.y,pos.roomName);
        this.structureType = structureType;
        this.structureId = undefined;
    }
    get x(){
        return this.pos.x;
    }
    get y(){
        return this.pos.y;
    }
    get roomName(){
        return this.pos.roomName;
    }
    serialize(){
        return {
            pos: this.pos,
            type: this.structureType,
            id: this.structureId,
        };
    }
    deserialize(obj){
        this.pos = obj.pos;
        this.structureType = obj.type;
        this.structureId = obj.id;
        return this;
    }
    isPlanned(){
        let room = Game.rooms[this.pos.roomName];
        if(!room){
            return undefined; //construction sites will not bring vision
        }
        
        let looks = room.lookForAt(this.structureType, this.pos);
        for(let look of looks){
            if(look instanceof Creep){
                return look.memory.role === "structure";
            }
            return true;
        }
        looks = room.lookForAt(LOOK_CONSTRUCTION_SITES, this.pos);
        for(let look of looks){
            if(look.structureType === this.structureType){
                return true;
            }
        }
        return false;
    }
}

export class DeterminedPlan extends Array{
    /**
     * 
     * @param {Room[]|PlanedStructure[]|[String,...(StructureConstant)]} args
     */
    constructor(...args){
        if(args.length === 0){
            return;
        }
        if((args[0] instanceof PlanedStructure)){
            this.setByList(args);
            return;
        }
        if(args[0] instanceof Room){
            this.setByRoom(args[0]);
            return;
        }
        if(args[0] instanceof String){
            this.setByMap(args[0],args[1]);
            return;
        }
        throw new Error("invalid arguments");
    }

    serialize(){
        let list = [];
        for(let plan of this){
            list.push(plan.serialize());
        }
        return {
            list: list,
        }
    }

    deserialize(obj){
        this.clear();
        for(let plan of obj.list){
            this.push(new PlanedStructure().deserialize(plan));
        }
    }

    clear(){
        if(this.length>0){
            this.splice(0);
        }
    }

    /**
     * 
     * @param {Room} room 
     */
    setByRoom(room){
        this.clear();
        let targets = room.find(FIND_MY_STRUCTURES);
        for(let structure of targets){
            this.push(new PlanedStructure(structure.pos, structure.structureType));
        }

        targets = room.find(FIND_MY_CONSTRUCTION_SITES);
        for(let site of targets){
            this.push(new PlanedStructure(site.pos, site.structureType));
        }

        // targets = room.find(FIND_MY_CREEPS,{filter:(creep)=>creep.memory.structurized});
        // for(let creep of targets){
        //     this.push(new PlanedStructure(creep.pos, "creep"));
        // }
    }

    /**
     * 
     * @param {typeof Structure[][]} map 
     * @param {String} roomName 
     */
    setByMap(roomName, map){
        this.clear();
        for(let i=0; i<map.length; i++){
            let row = map[i];
            for(let j=0; j<row.length; j++){
                this.push(new PlanedStructure(new RoomPosition(i,j,roomName),row[j]));
            }
        }
    }

    /**
     * 
     * @param {PlanedStructure[]} list
     */
    setByList(list){
        this.clear();
        for(let plan of list){
            this.push(plan);
        }
    }

    clone(){
        let clone = new DeterminedPlan();
        clone.setByList(this);
        return clone;
    }

    buildAll(){
        let notOkRet = OK;
        for(let structure of this){
            if(!(structure instanceof PlanedStructure)){
                errLog(new Error("type error: "+structure));
                continue;
            }
            let isPlanned = structure.isPlanned();
            if(isPlanned !== true){
                continue;
            }
            let room = Game.rooms[structure.roomName];
            // if(!room){//has been checked by isPlanned()
            //     continue;
            // }
            // if(structure.structureType === "creep"){
            //     continue;
            // }
            let ret = room.createConstructionSite(structure.pos, structure.structureType);
            if(ret !== OK){
                notOkRet = ret;
                console.log("create construction site not okay:",ret);
            }
        }
        return notOkRet;
    }

    /**
     * 
     * @param {RoomPosition} pos 
     * @returns {PlanedStructure[]}
     */
    getPlans(pos){
        let plans = [];
        for(let plan of this){
            if(!(plan instanceof PlanedStructure)){
                errLog(new Error("type error"))
                continue;
            }
            if(plan.pos.isEqualTo(pos)){
                plans.push(plan);
            }
        }
        return plans;
    }
    isDone(){
        for(let plan of this){
            if(!(plan instanceof PlanedStructure)){
                errLog(new Error("type error"))
                continue;
            }
            if(!plan.isPlanned()){
                return false;
            }
        }
        return true;
    }
}

export class RelevantPlan{

    /**
     * 
     * @param {(BuildableStructureConstant)[][]} map
     */
    constructor(map = undefined, rootPos = {x:0,y:0}){
        if(map !== undefined){
            this.map = map;
            this.rootPos = rootPos;
        }
    }

    serialize(){
        let list = [];
        this.check({x:0,y:0},(structureType, x, y)=>{
            list.push({
                structureType: structureType,
                x:x,
                y:y,
            });
        });
        return {
            list: list,
            rootPos: this.rootPos,
        }
    }
    deserialize(obj){
        this.rootPos = obj.rootPos;

        let width = 0;
        let height = 0;
        for(let node of obj.list){
            if(node.x>width){
                width = node.x+1;
            }
            if(node.y>height){
                height = node.y+1;
            }
        }

        for(let row=0; row<height; row++){
            this.map[row] = [];
        }

        for(let node of obj.list){
            this.map[node.y][node.x] = node.structureType;
        }
        return this;
    }

    /**
     * 
     * @param {RoomPosition|{x:Number,y:Number}} rootPos 
     * @param {(structureType:(StructureConstant),x:Number,y:Number)=>Boolean} predicate 
     * @param {Boolean} useRawPosition
     */
    check(rootPos, predicate, useRawPosition = false){
        for(let y=0; y<this.map.length; y++){
            let row = this.map[y];
            if(!row || row.length === 0){
                continue;
            }
            let yOffset = y-this.rootPos.y+rootPos.y;

            for(let x=0; x<row.length; x++){
                let structureType = row[x];
                if(!structureType){
                    continue;
                }
                let xOffset = x-this.rootPos.x+rootPos.x;

                if(predicate(structureType, useRawPosition?x:xOffset, useRawPosition?y:yOffset) === false){
                    return false;
                }
            }
        }
        return true;
    }

    canFit(rootPos){
        let room = Game.rooms[rootPos.roomName];
        if(!room){
            return undefined;
        }
        let terrain = getRoomTerrain(room);
        return this.check(rootPos, 
            (_structureType, x, y)=>terrain.get(x,y) !== TERRAIN_MASK_WALL);
    }
    /**
     * 
     * @param {RoomPosition} rootPos 
     */
    getPlanList(rootPos){
        if(this._planList instanceof DeterminedPlan){
            return this._planList;
        }

        this._planList = new DeterminedPlan();

        let room = Game.rooms[rootPos.roomName];
        if(!room){
            return this._planList;
        }

        this.check(rootPos, (structureType, x, y)=>{
            let pos = room.getPositionAt(x,y);
            let plan = new PlanedStructure(pos, structureType);
            if(plan.isPlanned() === false){
                this._planList.push(plan);
            }
            return false;
        });

        return this._planList;
    }
}

export class Structure extends Serializable{
    /**
     * 
     * @param {Room} spawnRoom 
     */
    constructor(spawnRoom = undefined, rootPos = undefined){
        super(spawnRoom);
        if(rootPos !== undefined){
            this.rootPos = rootPos;
        }
    }

    getRclRequired(){
        return 0;
    }

    isDone(){
        return true;
    }

    serialize(){
        return {
            super: super.serialize(),
            rootPos: this.rootPos,
        };
    }
    deserialize(obj){
        super.deserialize(obj.super);
        this.rootPos = obj.rootPos;
        return this;
    }

    /**
     * 
     * @param {object} hint 
     */
    plan(_hint = undefined){
        return OK;
    }
}